from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from app.db.postgres import get_session
from app.models import FileAsset, FileMediaInfo, ActivityFileAsset, EventFileAsset
from app.schemas.file_asset import FileAssetCreate, FileAssetRead, FileMediaInfo as FileMediaInfoSchema, FileBindPayload
from app.config import settings
import httpx
import hashlib


router = APIRouter(prefix="/file-assets", tags=["file-assets"]) 


@router.post("/register", response_model=FileAssetRead)
async def register_file_asset(payload: FileAssetCreate, session: AsyncSession = Depends(get_session)):
    data = payload.model_dump()
    stmt = select(FileAsset).where(FileAsset.storage_key == data["storage_key"])
    res = await session.execute(stmt)
    existing = res.scalar_one_or_none()
    if existing:
        for k, v in data.items():
            setattr(existing, k, v)
        await session.flush()
        await session.refresh(existing)
        await session.commit()
        media = await session.get(FileMediaInfo, existing.id)
        obj = FileAssetRead.model_validate(existing)
        if media:
            obj.media_info = FileMediaInfoSchema.model_validate(media)
        return obj
    obj = FileAsset(**data, is_deleted=False)
    session.add(obj)
    await session.flush()
    await session.refresh(obj)
    await session.commit()
    media = await session.get(FileMediaInfo, obj.id)
    out = FileAssetRead.model_validate(obj)
    if media:
        out.media_info = FileMediaInfoSchema.model_validate(media)
    return out


@router.get("/{file_id}", response_model=FileAssetRead)
async def get_file_asset(file_id: str, session: AsyncSession = Depends(get_session)):
    obj = await session.get(FileAsset, file_id)
    if not obj:
        raise HTTPException(status_code=404, detail="文件不存在")
    media = await session.get(FileMediaInfo, obj.id)
    out = FileAssetRead.model_validate(obj)
    if media:
        out.media_info = FileMediaInfoSchema.model_validate(media)
    return out


@router.get("")
async def list_file_assets(
    bucket: str | None = None,
    mime_type: str | None = None,
    activity_id: str | None = None,
    event_id: str | None = None,
    role: str | None = None,
    page: int = 1,
    page_size: int = 20,
    session: AsyncSession = Depends(get_session),
):
    stmt = select(FileAsset)
    count_stmt = select(func.count()).select_from(FileAsset)
    conds = []
    if bucket:
        from sqlalchemy import or_
        conds.append(or_(FileAsset.bucket == bucket, FileAsset.storage_key.ilike(f"{bucket}/%")))
    if mime_type:
        conds.append(FileAsset.mime_type.ilike(f"{mime_type}%"))
    if activity_id:
        from uuid import UUID
        try:
            _uuid = UUID(activity_id)
            if role:
                af_stmt = select(ActivityFileAsset.file_id).where(ActivityFileAsset.activity_id == _uuid, ActivityFileAsset.role == role)
            else:
                af_stmt = select(ActivityFileAsset.file_id).where(ActivityFileAsset.activity_id == _uuid)
            af_res = await session.execute(af_stmt)
            file_ids = [x for x, in af_res.all()]
            if file_ids:
                from sqlalchemy import or_
                conds.append(FileAsset.id.in_(file_ids))
        except Exception:
            pass
    if event_id:
        from uuid import UUID
        try:
            _uuid = UUID(event_id)
            if role:
                ef_stmt = select(EventFileAsset.file_id).where(EventFileAsset.event_id == _uuid, EventFileAsset.role == role)
            else:
                ef_stmt = select(EventFileAsset.file_id).where(EventFileAsset.event_id == _uuid)
            ef_res = await session.execute(ef_stmt)
            file_ids = [x for x, in ef_res.all()]
            if file_ids:
                conds.append(FileAsset.id.in_(file_ids))
        except Exception:
            pass
    if conds:
        from sqlalchemy import and_
        stmt = stmt.where(and_(*conds))
        count_stmt = count_stmt.where(and_(*conds))
    total_res = await session.execute(count_stmt)
    total = total_res.scalar() or 0
    res = await session.execute(
        stmt.order_by(FileAsset.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
    )
    items = [FileAssetRead.model_validate(x) for x in res.scalars().all()]
    return {"items": items, "total": total}


@router.post("/{file_id}/media")
async def upsert_media_info(file_id: str, payload: FileMediaInfoSchema, session: AsyncSession = Depends(get_session)):
    obj = await session.get(FileAsset, file_id)
    if not obj:
        raise HTTPException(status_code=404, detail="文件不存在")
    data = payload.model_dump(exclude_unset=True)
    media = await session.get(FileMediaInfo, obj.id)
    if media:
        for k, v in data.items():
            setattr(media, k, v)
    else:
        media = FileMediaInfo(file_id=obj.id, **data)
        session.add(media)
    await session.flush()
    await session.refresh(media)
    await session.commit()
    return {"ok": True}


@router.post("/upload")
async def upload_via_server(
    activity_id: str = Form(...),
    role: str | None = Form(None),
    file: UploadFile = File(...),
    session: AsyncSession = Depends(get_session),
):
    key = f"uploads/activity/{activity_id}/{role or 'other'}/{file.filename}"
    base = settings.FILE_UPLOAD_BASE.rstrip('/')
    url_put = f"{base}/{key}"
    content = await file.read()
    sum_hex = hashlib.sha256(content).hexdigest()
    auth_header = {}
    if settings.FILE_UPLOAD_AUTH:
        v = settings.FILE_UPLOAD_AUTH
        auth_header["Authorization"] = v if v.startswith("Basic ") else f"Basic {v}"
    elif settings.FILE_UPLOAD_USER and settings.FILE_UPLOAD_PASS:
        import base64
        token = base64.b64encode(f"{settings.FILE_UPLOAD_USER}:{settings.FILE_UPLOAD_PASS}".encode()).decode()
        auth_header["Authorization"] = f"Basic {token}"
    ct = file.content_type or "application/octet-stream"
    async with httpx.AsyncClient(timeout=None, trust_env=False) as client:
        resp = await client.put(url_put, headers={**auth_header, "Content-Type": ct}, content=content)
    if resp.status_code >= 300:
        raise HTTPException(status_code=resp.status_code, detail=f"上传失败: {resp.text}")
    access_base = settings.FILE_ACCESS_BASE.rstrip('/')
    url_access = f"{access_base}/{key}"
    data = {
        "storage_key": key,
        "url": url_access,
        "original_name": file.filename,
        "mime_type": ct,
        "size_bytes": len(content),
        "checksum_sha256": sum_hex,
        "bucket": "uploads",
        "visibility": "public",
        "storage_provider": "nginx-webdav",
    }
    stmt = select(FileAsset).where(FileAsset.storage_key == data["storage_key"])
    res = await session.execute(stmt)
    existing = res.scalar_one_or_none()
    if existing:
        for k, v in data.items():
            setattr(existing, k, v)
        await session.flush()
        await session.refresh(existing)
        await session.commit()
        file_id = existing.id
    else:
        obj = FileAsset(**data, is_deleted=False)
        session.add(obj)
        await session.flush()
        await session.refresh(obj)
        await session.commit()
        file_id = obj.id
    from uuid import UUID
    try:
        aid = UUID(activity_id)
        fid = UUID(str(file_id))
        b_stmt = select(ActivityFileAsset).where(ActivityFileAsset.activity_id == aid, ActivityFileAsset.file_id == fid)
        b_res = await session.execute(b_stmt)
        bind = b_res.scalar_one_or_none()
        if bind:
            bind.role = role or ""
        else:
            bind = ActivityFileAsset(activity_id=aid, file_id=fid, role=role or "")
            session.add(bind)
        await session.flush()
        await session.commit()
    except Exception:
        pass
    return {"id": str(file_id)}


@router.post("/activities/{activity_id}/files")
async def bind_activity_file(activity_id: str, payload: FileBindPayload, session: AsyncSession = Depends(get_session)):
    from uuid import UUID
    aid = UUID(activity_id)
    fid = UUID(str(payload.file_id))
    stmt = select(ActivityFileAsset).where(ActivityFileAsset.activity_id == aid, ActivityFileAsset.file_id == fid)
    res = await session.execute(stmt)
    bind = res.scalar_one_or_none()
    if bind:
        bind.role = payload.role
    else:
        bind = ActivityFileAsset(activity_id=aid, file_id=fid, role=payload.role)
        session.add(bind)
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.get("/activities/{activity_id}/files")
async def list_activity_files(activity_id: str, role: str | None = None, page: int = 1, page_size: int = 50, session: AsyncSession = Depends(get_session)):
    from uuid import UUID
    try:
        aid = UUID(activity_id)
    except Exception:
        raise HTTPException(status_code=400, detail="活动ID无效")
    from sqlalchemy import select, and_, func
    af_stmt = select(ActivityFileAsset.file_id, ActivityFileAsset.role).where(ActivityFileAsset.activity_id == aid)
    if role:
        af_stmt = af_stmt.where(ActivityFileAsset.role == role)
    af_res = await session.execute(af_stmt)
    pairs = af_res.all()
    file_ids = [fid for fid, _ in pairs]
    roles_map = {fid: r for fid, r in pairs}
    total = len(file_ids)
    if not file_ids:
        return {"items": [], "total": 0}
    stmt = select(FileAsset).where(FileAsset.id.in_(file_ids)).order_by(FileAsset.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
    res = await session.execute(stmt)
    items = []
    for fa in res.scalars().all():
        obj = FileAssetRead.model_validate(fa)
        media = await session.get(FileMediaInfo, fa.id)
        if media:
            obj.media_info = FileMediaInfoSchema.model_validate(media)
        items.append({"file": obj, "role": roles_map.get(fa.id)})
    return {"items": items, "total": total}


@router.put("/activities/{activity_id}/files/{file_id}")
async def update_activity_file_binding(activity_id: str, file_id: str, payload: FileBindPayload, session: AsyncSession = Depends(get_session)):
    from uuid import UUID
    try:
        aid = UUID(activity_id)
        fid = UUID(file_id)
    except Exception:
        raise HTTPException(status_code=400, detail="ID无效")
    stmt = select(ActivityFileAsset).where(ActivityFileAsset.activity_id == aid, ActivityFileAsset.file_id == fid)
    res = await session.execute(stmt)
    bind = res.scalar_one_or_none()
    if not bind:
        raise HTTPException(status_code=404, detail="绑定不存在")
    bind.role = payload.role
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.delete("/activities/{activity_id}/files/{file_id}")
async def unbind_activity_file(activity_id: str, file_id: str, session: AsyncSession = Depends(get_session)):
    from uuid import UUID
    try:
        aid = UUID(activity_id)
        fid = UUID(file_id)
    except Exception:
        raise HTTPException(status_code=400, detail="ID无效")
    stmt = select(ActivityFileAsset).where(ActivityFileAsset.activity_id == aid, ActivityFileAsset.file_id == fid)
    res = await session.execute(stmt)
    bind = res.scalar_one_or_none()
    if not bind:
        raise HTTPException(status_code=404, detail="绑定不存在")
    await session.delete(bind)
    await session.commit()
    return {"ok": True}


@router.post("/events/{event_id}/files")
async def bind_event_file(event_id: str, payload: FileBindPayload, session: AsyncSession = Depends(get_session)):
    from uuid import UUID
    eid = UUID(event_id)
    fid = UUID(str(payload.file_id))
    stmt = select(EventFileAsset).where(EventFileAsset.event_id == eid, EventFileAsset.file_id == fid)
    res = await session.execute(stmt)
    bind = res.scalar_one_or_none()
    if bind:
        bind.role = payload.role
    else:
        bind = EventFileAsset(event_id=eid, file_id=fid, role=payload.role)
        session.add(bind)
    await session.flush()
    await session.commit()
    return {"ok": True}
